package com.lazi.june.web.version;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Version;
import com.lazi.june.common.base.RequestConst;
import com.lazi.june.web.WebProperties;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;

/**
 * desc: 版本路由
 *
 * @author huliqian
 * @since 2025/9/11
 */
@Slf4j
public class VersionedRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    private static final String DEFAULT_VERSION = "v1.0.0";
    private static final String DELIMITER = "#";
    private static final String ANY_METHOD = "*";
    private final Map<String, NavigableMap<Version, HandlerMethod>> versionedHandlers = new HashMap<>();
    private final WebProperties properties;

    public VersionedRequestMappingHandlerMapping(WebProperties properties) {
        this.properties = properties;
    }

    @Override
    protected void registerHandlerMethod(@NonNull Object handler, @NonNull Method method, @NonNull RequestMappingInfo mapping) {
        if (!properties.isVersionApiEnabled()) {
            super.registerHandlerMethod(handler, method, mapping);
            return;
        }

        // 方法优先，其次类
        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        if (apiVersion == null) {
            apiVersion = AnnotationUtils.findAnnotation(method.getDeclaringClass(), ApiVersion.class);
        }
        if (apiVersion == null) {
            super.registerHandlerMethod(handler, method, mapping);
            return;
        }

        // 支持多 pattern 和多 http method
        Set<String> patterns = mapping.getPatternValues();
        if (CollUtil.isEmpty(patterns)) {
            // 降级处理或记录日志
            super.registerHandlerMethod(handler, method, mapping);
            return;
        }
        Set<String> methods = new HashSet<>();

        mapping.getMethodsCondition().getMethods().forEach(m -> methods.add(m.name()));
        // 支持任意方法
        if (methods.isEmpty()) {
            methods.add(ANY_METHOD);
        }
        for (String pattern : patterns) {
            for (String httpMethod : methods) {
                HandlerMethod handlerMethod = super.createHandlerMethod(handler, method);
                String key = pattern + DELIMITER + httpMethod;
                versionedHandlers.computeIfAbsent(key, k -> new TreeMap<>()).put(new Version(apiVersion.value()), handlerMethod);
                if (log.isInfoEnabled()) {
                    log.info("Registered versioned handler method: {}-{}", key, handlerMethod + DELIMITER + apiVersion.value());
                }
            }
        }
    }

    @Override
    protected HandlerMethod lookupHandlerMethod(@NonNull String lookupPath, @NonNull HttpServletRequest request) throws Exception {
        if (!properties.isVersionApiEnabled()) {
            return super.lookupHandlerMethod(lookupPath, request);
        }
        return Optional.ofNullable(findVersionedHandler(lookupPath, request.getMethod(), request))
                .orElse(super.lookupHandlerMethod(lookupPath, request));
    }

    /**
     * 匹配版本
     */
    private HandlerMethod findVersionedHandler(String lookupPath, String httpMethod, HttpServletRequest request) {
        // 遍历所有 key，用 pathMatcher 匹配 pattern
        for (Map.Entry<String, NavigableMap<Version, HandlerMethod>> entry : versionedHandlers.entrySet()) {
            String[] parts = entry.getKey().split(DELIMITER);
            String pattern = parts[0];
            String method = parts[1];

            if (!method.equals(ANY_METHOD) && !method.equals(httpMethod)) {
                continue;
            }
            if (!getPathMatcher().match(pattern, lookupPath)) {
                continue;
            }

            String versionStr = Optional.ofNullable(request.getHeader(RequestConst.VERSION)).orElse(DEFAULT_VERSION);

            return Optional.ofNullable(entry.getValue().floorEntry(new Version(versionStr)))
                    .map(Map.Entry::getValue)
                    .orElse(entry.getValue().firstEntry().getValue());
        }
        return null;
    }

}
